﻿/*
库
	写好的现有的、成熟的、可以复用的代码。
	Linux中 静态库后缀：.a       动态库后缀：.so
	win 中	静态库后缀：.lib     动态库可以：.dll

ELF 有着四大块
	1.ELF  Header  
	2.Program  Header  Table
	3.Section 各个
	4.Section  Header  Table


静态库的制作
	1.动静态库中，不需要包含main函数    
		只要把 .o 文件 和 .h文件打包交给别人 ---> 就可以实现库的效果
		所有的库（无论是动还是静），本质都是源文件对应的 .o 
		静态库本质，就是 .o 文件打了一个包！
	
	将内容进行压缩tar
		tar  czf  lib.tgz  lib       把文件lib   压缩为lib.tgz
		tar  xzf  lib.tgz			 解压

下面的方法可以把所有的 .o 文件打包成为一个压缩包，不需要再解压就可以直接使用！（可直接识别）
		1.先把  所有的.c 文件变为同名 .o文件 gcc -c *.c       
		2.ar  -rc  mylibc.a  *.o			这就是把所有的 .o 文件打一个包，这个包的名字就是 mylibc.a

	·	.a 静态库，本质是一种归档文件，不需要使用者解包，而用gcc/g++ 直接进行链接即可！
		ar -rc mylibc.a *.o   ---->  ar表示归档的意思，rc表示（replace 替换    create 新建）
		
		静态库  最后一定要以 .a  结尾     以lib 作为开头 ----> 也就是说会形成 libmyc.a  的文件
		真正库的名字是 去掉lib  去掉 .a     myc

	·  也就是说  链接静态库的时候！ 只要 -l名字  -L.  即可     也就是说  -lmyc
	    gcc -o  usercode usercode.o -L. -lmyc

	-L 就是去哪里找库    -lmyc 就是找名字是啥的库
	也就是说：如果要去任何非C/C++标准库（包扩其他外部或者我们自己写的）  都需要指明 -l（库名字） -L（去哪找）

	·  gcc  -c  usercode.c  -I  ./lib/include 
	-I 表示gcc 在 查找头文件的时候不仅在（当前目录下查找）  也在 -I 指明的路径下查找

	gcc  -o  usercode  usercode.c  -I  ./lib/include/  -L  ./lib/mylib/ -l  myc
	-I  指定头文件搜索目录
	-L  指定库目录搜索库文件
	-l  指定链接哪个库  链接哪一个就直接写库名



整套操作流程
	mkdir  lib
	可以在目录下创建路径 ---> mkdir  -p  lib/include              mkdir  -p  lib/mylib
		然后就可以把所有的 .h 和 .a 的所有的文件都放在对应的路径下
		然后可以给lib  进行打个包 ---> tar  czf  lib.tgz  lib     (把lib 文件进行一个压缩 lib.tgz)
		也就对应成为了   安装包

	当有人下载下来之后 ---> 可以进行解包  
		tar  xzf  lib.tgz ----> 解压出来就是 lib
	然后编译的时候需要找到对应的 头文件   就需要使用 -I 指定目录下查找头文件
		gcc  -c  usercode.c  -I  ./lib/include      就形成了同名 .o 文件
	但是如果说想一步到位直接 -o(因为默认只在当前文件查找)
		gcc  usercode.c  -o  usercode  -I  ./lib/include  -L  ./lib/mylib/  -l  myc  （全链路）
当然如果说不想带着 -I  就直接在文件中  #include"./lib/include"   直接带上路径   就可以省略上面的方式

·	总结：对于库所谓的安装！实际就是把库对应的头文件和库文件，拷贝到系统指定路径下




动态库和静态库
	动态库和静态库原理基本一致，只是相比较下，把内容打包成为静态库是更为常规的操作！
	gcc  -fPIC  -c  *.c         形成 .o 文件  和静态库不太一样的就是只需要多加一个选项  -fPIC 
	·静态库打包是   ar     归档
	·动态库打包是   gcc    进行打包        gcc  -shared  -o  libmyc.so  *.o        这就是生成动态库
动态库必须是以 lib 开头， .so 结尾        gcc的时候 中间必须要加上  -shared
也就会对应生成一个  libmyc.so


	mkdir  lib  
	然后对应的 Makefile 中   也是需要进行修改
		libmyc.so:mystdio.c  mystdio.h
				gcc  -shared  -o  $@  $^
		mystdio.o:mystdio.c
				gcc  -fPIC  -c  $<
		mystdio.o:mystring.c
				gcc  -fPIC  -c  $<
		这个需要把内容做一个打包输出
		.PHONY:output
		output: 
				mkdir -p lib/include
				mkdir -p lib/mylib
				cp -f *.h lib/include      头文件给别人
				cp -f *.so lib/mylib       库也给别人
				tar czf lib.tgz lib        把lib文件进行压缩
				
		.PHONY:clean
		clean:
			rm  -rf  *.o  libmyc.so  lib  lib.tgz
			    

但是注意：当 gcc -o usercode usercode.c -I lib/include/ -L lib/mylib/ -lmyc    会对应生成可执行文件
但是执行生成的可执行程序会报链接错误（找不到对应的libmyc.so）   这是因为只告诉了  gcc  并没有告诉系统

·想要解决上面的问题
	1.把自己的库直接拷贝到总系统里   sudo cp lib/mylib/libmyc.so /lib64
	2.或者给默认路径建立软链接  指向对应的库
	3.echo $LD_LIBRARY_PATH   OS运行程序，需要查找动态库，也会在该环境变量下查找动态库
	  LD_LIBRARY_PATH    这个库经常情况下是 空的     用环境变量的做法
	4.新增配置文件    ls /etc/ld.so.conf.d/


gcc 是 不等于 系统的！！！（但是静态库没有这个问题）
静态库没有是因为 静态库链接的时候直接把静态库的实现拷贝到了可执行程序里，形成可执行程序就不会依赖静态库
（编译成功一定能执行！）
动态库需要加载可执行的同时，也要找到所依赖的库


动态库 和 静态库
	只有动态库 --> 动态链接
	动静态库都有 --> 动态链接
	动静都有想用静态 --> 后面要加上-static         想静态链接就必须  -static
	
结论：
	1.对于gcc 或者 g++ 都是默认使用动态库（而不是静态库）
	2.对于动静态库同时存在 一定要使用静态链接  而不是动态库  就只能 在后面带上 -static
	3.只有动态库后面加上 -static   ---> 是不能使用的  
	  也就是 有了  -static      就必须存在对应的静态库！
	  只存在静态库  可执行程序  就只能静态链接！

结论1：在Linux 系统下，默认情况安装的大部分库，默认都优先安装的是动态库！
结论2：库：应用程序 = 1：n     
结论3：vs不仅仅可以形成可执行程序，也能形成动静态库




目标文件（预处理，编译，汇编，链接）    重要的汇编  链接
	.o/.obj  可重定位目标文件     库的本质也是 .o
	设计上  建议把所有的文件 编译成为   .o  文件
	（然后再对所有的.o 文件进行链接  这样更改一个只需要编译一个.o  然后进行链接即可）

	.o 文件 -->  可重定位目标文件
	.so / .a  --> 库文件
	可执行程序就是可执行程序


结论：动静态库，可执行程序，.o文件都是ELF格式的！
	  动静态库，可执行程序，.o文件这些都是以一定的格式放入到二进制文件中的！
	可执行程序就是把代码数据，划分为不同的区，将相同属性的放在同一个区

ELF 形成可执行
	1.将多份C/C++ 源代码，翻译成为目标 .o文件 + 动静态库（ELF）     也就是动静态库  可执行程序  .o文件
	2.将多份 .o  文件section 节进行合并
  实际合并是在链接时进行的，但是并不是简单的合并，会涉及对库合并

字符常量区未来会和代码区进行合并，因为他们都是只读的，编译会形成很多的Section
虚拟地址空间是 加载到操作系统之前，操作系统要读取 ELF格式 ，然后用ELF中的相关字段初始化地址空间的结构体相关变量




ELF 可执行文件加载
目标：
	1.静态库是如何形成可执行程序的    ----  弱化
 · 2.ELF程序如何加载到内存的？       加载文件 就是找到文件（路径 + 文件名）
	ELF程序是如何转换成为进程的（逻辑地址，物理地址，虚拟地址），虚拟地址空间！
 · 3.动态库是如何和我们的可执行程序关联
 · 4.动态库是如何加载的

合并
	·一个ELF 会有多种不同的Section，在加载到内存的时候，也会进行Section合并，形成segment
	·合并原则：相同属性，譬如：可读，可写，可执行，需要加载时申请空间等
	  也就是说即便是不同的Section，在加载到内存中，可能会以segment 的形式，加载到一起
	·合并工作也已经在形成ELF 的时候，合并方式确定，具体合并原则被记录在ELF的程序头表中（program  header  table）


读取可执行层序的section  
	用工具  readelf   把一个个数据节读取出来   
		-S  把section header 读取出来  
		-l  查看section 合并的segment
		-h  查看ELF Header

为什么要把section 合并成为 segment
	1.Section 合并的主要原因是减少页面碎片化，提高内存使用效率，不进行合并的话，假设页面大小为4096字节
	（内存块基本大小，加载，管理的基本单位）若.text 部分为4097字节，.init部分就是512字节，他们将占用3个
	页面，合并之后，只需要2个页面！
	2.操作系统在加载程序时，会将具有相同属性的section合并称为一个大的segment，可以实现不同的访问权限，
	  优化内存管理和权限访问控制


静态库形成可执行程序（弱）
.symtab节 : Symbol Table 符号表，就是源码⾥⾯那些函数名、变量名和代码的对应关系。
	char类型的数组
ELF Header    整个ELF的管理信息




1.静态链接和加载
	静态链接
		进行编译变为同名 .o       gcc  -c  *.c
		进行两个文件链接    gcc  -o  main.exe  *.o
 
查看编译后的 .o 目标文件 
	工具：objdump        对目标文件进行反汇编     objdump  -d  code.o     -d表示只展示相关的方法
	如果说   只是对 .o 文件进行查看反汇编，并没有连接的时候 ---> call 的地址是全0 
	·即使没有对应的函数实现（也是可以编译成功   形成.o文件），只有在链接的时候才能够发现问题得到修正

静态链接 
	静态链接就是把库中的.o 进行合并
	链接其实就是将编译之后的所有目标文件连同用到的一些静态库运行时库组合，拼装成一个独立的可执行文件。
	其中包括之前提到的地址修正，当所有模块组合在一起之后，链接器会根据.o文件或者静态库中的重定位表找到
	那些需要被重定位的函数全局变量，从而修正它们的地址。这其实就是静态链接的过程。

注：链接过程中会涉及到 .o 中 外部符号进行地址重定位。  也就是链接重定位，因为在链接的时候，反汇编call后地址
会被重新修改，所以叫  可重定位目标文件     可以修改


函数声明不需要加extern   没有函数体就是函数声明
变量声明是需要加extern的



2.ELF 程序如何加载到内存？（找到它，路径+文件名），ELF程序是怎么转换成为进程的？（逻辑地址，物理地址，虚拟地址）
虚拟地址空间
	问题：一个可执行程序，如果没有被加载到内存中，该可执行程序，有没有地址？
	答案：有地址的！
阶段一
当确保  .text 和 .data 区没有交集，就可以把所有的可执行程序，完成在磁盘上的编址
当call 操作的时候就用  起始地址 + 偏移量 的方式       这就是逻辑地址  call  funaddr

阶段二
	如果每个segment 的开始地址都是0呢？（32位 && 64位）
	所有的可执行程序，就是一个segment，所有的segment，所有函数，所有的变量编址，起始偏移量都从0开始！统一向下编址
	从零开始进行编址（虚拟地址空间）（默认从零开始   平坦模式编址）

虚拟地址空间 不仅仅是进程看待内存的方式！ 磁盘上的可执行程序，代码和数据编址其实就是虚拟地址的统一编址

objdump -d main.exe     使用这个命令的时候  最左侧就是ELF虚拟地址，严格叫做逻辑地址（起始地址 + 偏移量）
						起始地址认为是0，所以虚拟地址在我们程序还没加载到内存，就能把可执行程序进行统一编址

页表中一侧是虚拟地址   一侧是物理地址   映射一一对应
可执行入口是被记录在ELF 的Header中，磁盘上叫逻辑地址，在CPU中叫虚拟地址

逻辑地址  虚拟地址  线性地址三位一体，     在磁盘上叫逻辑地址，起始值为0
在Linux中叫做  虚拟地址                    
物理地址是加载到内存中代码、数据所对应的地址


具体过程：
	1.先找到磁盘文件中的数据块
	2.然后哭加载（磁盘  main.exe）    库内容其实就是在文件内核缓冲区中
	3.映射（通过页表进行映射）
	4.得到库起始虚拟地址




3.动态库是如何和可执行程序关联？
	库函数调用：
		1.被进程看到：动态库映射到进程的地址空间
		2.被进程调用：在进程的地址空间中进行跳转
动态库的本质就是：把在系统层面上，公共的代码抽取出来，动态库的代码不会出现重复！
				  把所有代码放在一起，代码是只读的（这就是动态库也叫共享库）




4.动态链接
	动态链接实际上将链接的整个过程推迟到了程序加载的时候（需要加载的时候才进行加载）

  ·动态库也是ELF，理解成为：起始地址（0）+ 偏移量

	C/C++程序开始执行，不会直接跳到main函数，实际，程序的入口点是  _start,是一个由C运行时库（通常是glibc）
或者链接器(ld) 提供的特殊函数
	1. 设置堆栈：为程序创建一个初始的堆栈环境。
	2. 初始化数据段：将程序的数据段（如全局变量和静态变量）从初始化数据段复制到相应的内存位
	   置，并清零未初始化的数据段。
	3. 动态链接：这是关键的一步，_start 函数会调用动态链接器的代码来解析和加载程序所依赖的
	   动态库（shared libraries）。动态链接器会处理所有的符号解析和重定位，确保程序中的函数调
	   用和变量访问能够正确地映射到动态库中的实际地址。
	4. 调用 __libc_start_main ：一旦动态链接完成， _start 函数会调用
		__libc_start_main （这是glibc提供的一个函数）。 __libc_start_main 函数负责执行
		一些额外的初始化工作，比如设置信号处理函数、初始化线程库（如果使用了线程）等。
	5. 调用main 函数：最后， __libc_start_main 函数会调用程序的 main 函数，此时程序的执
	   行控制权才正式交给用户编写的代码。
	6. 处理 main 函数的返回值：当 main 函数返回时， __libc_start_main 会负责处理这个返回
	   值，并最终调用 _exit 函数来终止程序


动态链接器:
◦ 动态链接器（如ld-linux.so）负责在程序运行时加载动态库。
◦ 当程序启动时，动态链接器会解析程序中的动态库依赖，并加载这些库到内存中。
  环境变量和配置文件
◦ Linux系统通过环境变量（如LD_LIBRARY_PATH）和配置文件（如/etc/ld.so.conf及其子配置
  文件）来指定动态库的搜索路径。
◦ 这些路径会被动态链接器在加载动态库时搜索。

缓存文件:
◦ 为了提高动态库的加载效率，Linux系统会维护一个名为/etc/ld.so.cache的缓存文件。
◦ 该文件包含了系统中所有已知动态库的路径和相关信息，动态链接器在加载动态库时会首先
  搜索这个缓存文件。


1. 由于代码段只读，我们不能直接修改代码段。但有了GOT表，代码便可以被所有进程共享。但在不
   同进程的地址空间中，各动态库的绝对地址、相对位置都不同。反映到GOT表上，就是每个进程的
   每个动态库都有独立的GOT表，所以进程间不能共享GOT表。
2. 在单个.so下，由于GOT表与 .text 的相对位置是固定的，我们完全可以利用CPU的相对寻址来找
   到GOT表。
3. 在调用函数的时候会首先查表，然后根据表中的地址来进行跳转，这些地址在动态库加载的时候会
   被修改为真正的地址。
4. 这种方式实现的动态链接就被叫做 PIC 地址无关代码 。换句话说，我们的动态库不需要做任何修
   改，被加载到任意内存地址都能够正常运行，并且能够被所有进程共享，这也是为什么之前我们给
   编译器指定-fPIC参数的原因，PIC=相对编址+GOT。


库中是有依赖的
• 不仅仅有可执行程序调用库
• 库也会调用其他库！！库之间是有依赖的，如何做到库和库之间互相调用也是与地址无关的呢？？
• 库中也有.GOT,和可执行一样！这也就是为什么都是ELF的格式！


• 静态链接的出现，提高了程序的模块化水平。对于一个大的项目，不同的人可以独立地测试和开发 
  自己的模块。通过静态链接，生成最终的可执行文件。
• 我们知道静态链接会将编译产生的所有目标文件，和用到的各种库合并成一个独立的可执行文件，
  其中我们会去修正模块间函数的跳转地址，也被叫做编译重定位(也叫做静态重定位)。
• 动态链接实际上将链接的整个过程推迟到了程序加载的时候。比如运行一个程序，操作系
  统会先将程序的数据代码连同它用到的一系列动态库先加载到内存，其中每个动态库的加载地址
  都是不固定的，但无论加载到什么地方，都要映射到进程对应的地址空间，然后通过.GOT方式进
  行调用(运行重定位，也叫做动态地址重定位)。
*/